package org.xtext.runtime.eis;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.xtext.runtime.interpreter.Agent;
import eis.AgentListener;
import eis.EILoader;
import eis.EnvironmentInterfaceStandard;
import eis.EnvironmentListener;
import eis.exceptions.ActException;
import eis.exceptions.AgentException;
import eis.exceptions.EntityException;
import eis.exceptions.ManagementException;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.PerceiveException;
import eis.exceptions.RelationException;
import eis.iilang.Action;
import eis.iilang.EnvironmentState;
import eis.iilang.Parameter;
import eis.iilang.Percept;


public class EISService {
	private EnvironmentInterfaceStandard ei = null;
	private Map<String, List<Percept>> events = new Hashtable<String, List<Percept>>();
	private List<EISEvent> environmentEventQueue = new LinkedList<EISEvent>();
	
	private class EISAgentListener implements AgentListener {
		@Override
		public void handlePercept(String agentName, Percept percept) {
			events.get(agentName).add(percept);
		}
	}
	
	private class EISEnvironmentListener implements EnvironmentListener {
		@Override
		public void handleNewEntity(String event) {
			environmentEventQueue.add(new EISEvent("newEntity", new Object[] { event }));
		}

		@Override
		public void handleDeletedEntity(String entity, Collection<String> agents) {
			environmentEventQueue.add(new EISEvent("deletedEntity", new Object[] { entity }));
		}

		@Override
		public void handleFreeEntity(String entity, Collection<String> agents) {
			environmentEventQueue.add(new EISEvent("freedEntity", new Object[] { entity }));
		}

		@Override
		public void handleStateChange(EnvironmentState newState) {
			// TODO Auto-generated method stub
			
		}
	}

	public boolean eisStart() {
		try {
			ei.start();
		} catch (ManagementException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
	public void eisInit(Map<String, Parameter> parameters) throws ManagementException {
		ei.init(parameters);
	}
	
	public boolean isStartSupported() {
		return ei.isStartSupported();
	}
	
	public String getEnvironmentState() {
		EnvironmentState state = ei.getState();
		if (state.equals(EnvironmentState.PAUSED)) {
			return "paused";
		} else if (state.equals(EnvironmentState.INITIALIZING)) {
			return "initializing";
		} else if (state.equals(EnvironmentState.RUNNING)) {
			return "running";
		} else if (state.equals(EnvironmentState.KILLED)) {
			return "killed";
		}
		return "unknown";
	}
	
	public EISEvent getNextEnvironmentEvent() {
		return environmentEventQueue.remove(0);
	}
	
	public boolean hasEnvironmentEvent() {
		return !environmentEventQueue.isEmpty();
	}
	
    public void setup(String jarFile) {
		try {
			if (jarFile.startsWith("jar:")) {
				System.out.println("[UNSUPPORTED] Using URL-based download: " + jarFile);
//				ei = EILoader.fromJarFile(new URL(jarFile));
			} else {
				ei = EILoader.fromJarFile(new File(jarFile));
			}
			EISEnvironmentListener listener = new EISEnvironmentListener();
			ei.attachEnvironmentListener(listener);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
    
    public void registerAgent(Agent agent) {
    	try {
			ei.registerAgent(agent.name());
			events.put(agent.name(), new LinkedList<Percept>());
			ei.attachAgentListener(agent.name(), new EISAgentListener());
		} catch (AgentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
    
    public void unregisterAgent(Agent agent) {
    	try {
			ei.unregisterAgent(agent.name());
		} catch (AgentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
    
    public Collection<String> getFreeEntities() {
    	return ei.getFreeEntities();
    }
    
    public boolean associateEntity(Agent agent, String entity) {
    	try {
			ei.associateEntity(agent.name(), entity);
		} catch (RelationException e) {
			// TODO Auto-generated catch block
			return false;
		}
		return true;
    }

    public String queryEntityType(String entity) {
    	try {
			return ei.getType(entity);
		} catch (EntityException e) {
		}
		return null;
    }

    public Map<String, Percept> performAction(String entity, Action act) throws ActException, NoEnvironmentException {
//    	System.out.println(act.toXML());
		return ei.performAction(entity, act);
		
    }
    
    /**
     * Make defensive copy of perceptions from EIS and return (method is synchronized to ensure that
     * there is not concurrent access to the DS.
     * @param agent
     * @return
     * @throws PerceiveException
     * @throws NoEnvironmentException
     */
    public synchronized Map<String, Collection<Percept>> getPercepts(Agent agent) throws PerceiveException, NoEnvironmentException {
    	Map<String, Collection<Percept>> percepts = ei.getAllPercepts(agent.name(), agent.name());
    	Map<String, Collection<Percept>> copy = new HashMap<String, Collection<Percept>>();
    	for (String key : percepts.keySet()) {
    		Collection<Percept> list = new LinkedList<Percept>();
    		for (Percept p: percepts.get(key)) {
    			list.add(p);
    		}
    		copy.put(key, list);
    	}
    	return copy;
    }
    
    public List<Percept> getAgentEvents(Agent agent) {
    	return events.get(agent.name());
    }

    // Map to store active EIS instances for the given platform...
    private static Map<String, EISService> services = new HashMap<String, EISService>();
    
	public static synchronized EISService getService(String id, String jar) {
		EISService service = services.get(id);
		if (service == null) {
			service = new EISService();
			service.setup(jar);
			services.put(id, service);
		}
		return service;
	}

	public static EISService getService(String id) {
		return services.get(id);
	}
    
}
